<?php
/**
 * Zend Framework
 *
 * LICENSE
 *
 * This source file is subject to the new BSD license that is bundled
 * with this package in the file LICENSE.txt.
 * It is also available through the world-wide-web at this URL:
 * http://framework.zend.com/license/new-bsd
 * If you did not receive a copy of the license and are unable to
 * obtain it through the world-wide-web, please send an email
 * to license@zend.com so we can send you a copy immediately.
 *
 * @category   Zend
 * @package    Zend_Controller
 * @subpackage Zend_Controller_Action_Helper
 * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
 * @license    http://framework.zend.com/license/new-bsd     New BSD License
 * @version    $Id: ContextSwitch.php 23775 2011-03-01 17:25:24Z ralph $
 */

/**
 * @see Zend_Controller_Action_Helper_Abstract
 */
require_once 'Zend/Controller/Action/Helper/Abstract.php';

/**
 * Simplify context switching based on requested format
 *
 * @uses       Zend_Controller_Action_Helper_Abstract
 * @category   Zend
 * @package    Zend_Controller
 * @subpackage Zend_Controller_Action_Helper
 * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
 * @license    http://framework.zend.com/license/new-bsd     New BSD License
 */
class Zend_Controller_Action_Helper_ContextSwitch extends Zend_Controller_Action_Helper_Abstract {
	/**
	 * Trigger type constants
	 */
	const TRIGGER_INIT = 'TRIGGER_INIT';
	const TRIGGER_POST = 'TRIGGER_POST';
	
	/**
	 * Supported contexts
	 * @var array
	 */
	protected $_contexts = array ();
	
	/**
	 * JSON auto-serialization flag
	 * @var boolean
	 */
	protected $_autoJsonSerialization = true;
	
	/**
	 * Controller property key to utilize for context switching
	 * @var string
	 */
	protected $_contextKey = 'contexts';
	
	/**
	 * Request parameter containing requested context
	 * @var string
	 */
	protected $_contextParam = 'format';
	
	/**
	 * Current context
	 * @var string
	 */
	protected $_currentContext;
	
	/**
	 * Default context (xml)
	 * @var string
	 */
	protected $_defaultContext = 'xml';
	
	/**
	 * Whether or not to disable layouts when switching contexts
	 * @var boolean
	 */
	protected $_disableLayout = true;
	
	/**
	 * Methods that require special configuration
	 * @var array
	 */
	protected $_specialConfig = array ('setSuffix', 'setHeaders', 'setCallbacks' );
	
	/**
	 * Methods that are not configurable via setOptions and setConfig
	 * @var array
	 */
	protected $_unconfigurable = array ('setOptions', 'setConfig', 'setHeader', 'setCallback', 'setContext', 'setActionContext', 'setActionContexts' );
	
	/**
	 * @var Zend_Controller_Action_Helper_ViewRenderer
	 */
	protected $_viewRenderer;
	
	/**
	 * Original view suffix prior to detecting context switch
	 * @var string
	 */
	protected $_viewSuffixOrig;
	
	/**
	 * Constructor
	 *
	 * @param  array|Zend_Config $options
	 * @return void
	 */
	public function __construct($options = null) {
		if ($options instanceof Zend_Config) {
			$this->setConfig ( $options );
		} elseif (is_array ( $options )) {
			$this->setOptions ( $options );
		}
		
		if (empty ( $this->_contexts )) {
			$this->addContexts ( array ('json' => array ('suffix' => 'json', 'headers' => array ('Content-Type' => 'application/json' ), 'callbacks' => array ('init' => 'initJsonContext', 'post' => 'postJsonContext' ) ), 'xml' => array ('suffix' => 'xml', 'headers' => array ('Content-Type' => 'application/xml' ) ) ) );
		}
		
		$this->init ();
	}
	
	/**
	 * Initialize at start of action controller
	 *
	 * Reset the view script suffix to the original state, or store the
	 * original state.
	 *
	 * @return void
	 */
	public function init() {
		if (null === $this->_viewSuffixOrig) {
			$this->_viewSuffixOrig = $this->_getViewRenderer ()->getViewSuffix ();
		} else {
			$this->_getViewRenderer ()->setViewSuffix ( $this->_viewSuffixOrig );
		}
	}
	
	/**
	 * Configure object from array of options
	 *
	 * @param  array $options
	 * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface
	 */
	public function setOptions(array $options) {
		if (isset ( $options ['contexts'] )) {
			$this->setContexts ( $options ['contexts'] );
			unset ( $options ['contexts'] );
		}
		
		foreach ( $options as $key => $value ) {
			$method = 'set' . ucfirst ( $key );
			if (in_array ( $method, $this->_unconfigurable )) {
				continue;
			}
			
			if (in_array ( $method, $this->_specialConfig )) {
				$method = '_' . $method;
			}
			
			if (method_exists ( $this, $method )) {
				$this->$method ( $value );
			}
		}
		return $this;
	}
	
	/**
	 * Set object state from config object
	 *
	 * @param  Zend_Config $config
	 * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface
	 */
	public function setConfig(Zend_Config $config) {
		return $this->setOptions ( $config->toArray () );
	}
	
	/**
	 * Strategy pattern: return object
	 *
	 * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface
	 */
	public function direct() {
		return $this;
	}
	
	/**
	 * Initialize context detection and switching
	 *
	 * @param  mixed $format
	 * @throws Zend_Controller_Action_Exception
	 * @return void
	 */
	public function initContext($format = null) {
		$this->_currentContext = null;
		
		$controller = $this->getActionController ();
		$request = $this->getRequest ();
		$action = $request->getActionName ();
		
		// Return if no context switching enabled, or no context switching
		// enabled for this action
		$contexts = $this->getActionContexts ( $action );
		if (empty ( $contexts )) {
			return;
		}
		
		// Return if no context parameter provided
		if (! $context = $request->getParam ( $this->getContextParam () )) {
			if ($format === null) {
				return;
			}
			$context = $format;
			$format = null;
		}
		
		// Check if context allowed by action controller
		if (! $this->hasActionContext ( $action, $context )) {
			return;
		}
		
		// Return if invalid context parameter provided and no format or invalid
		// format provided
		if (! $this->hasContext ( $context )) {
			if (empty ( $format ) || ! $this->hasContext ( $format )) {
				
				return;
			}
		}
		
		// Use provided format if passed
		if (! empty ( $format ) && $this->hasContext ( $format )) {
			$context = $format;
		}
		
		$suffix = $this->getSuffix ( $context );
		
		$this->_getViewRenderer ()->setViewSuffix ( $suffix );
		
		$headers = $this->getHeaders ( $context );
		if (! empty ( $headers )) {
			$response = $this->getResponse ();
			foreach ( $headers as $header => $content ) {
				$response->setHeader ( $header, $content );
			}
		}
		
		if ($this->getAutoDisableLayout ()) {
			/**
			 * @see Zend_Layout
			 */
			require_once 'Zend/Layout.php';
			$layout = Zend_Layout::getMvcInstance ();
			if (null !== $layout) {
				$layout->disableLayout ();
			}
		}
		
		if (null !== ($callback = $this->getCallback ( $context, self::TRIGGER_INIT ))) {
			if (is_string ( $callback ) && method_exists ( $this, $callback )) {
				$this->$callback ();
			} elseif (is_string ( $callback ) && function_exists ( $callback )) {
				$callback ();
			} elseif (is_array ( $callback )) {
				call_user_func ( $callback );
			} else {
				/**
				 * @see Zend_Controller_Action_Exception
				 */
				require_once 'Zend/Controller/Action/Exception.php';
				throw new Zend_Controller_Action_Exception ( sprintf ( 'Invalid context callback registered for context "%s"', $context ) );
			}
		}
		
		$this->_currentContext = $context;
	}
	
	/**
	 * JSON context extra initialization
	 *
	 * Turns off viewRenderer auto-rendering
	 *
	 * @return void
	 */
	public function initJsonContext() {
		if (! $this->getAutoJsonSerialization ()) {
			return;
		}
		
		$viewRenderer = Zend_Controller_Action_HelperBroker::getStaticHelper ( 'viewRenderer' );
		$view = $viewRenderer->view;
		if ($view instanceof Zend_View_Interface) {
			$viewRenderer->setNoRender ( true );
		}
	}
	
	/**
	 * Should JSON contexts auto-serialize?
	 *
	 * @param  boolean $flag
	 * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface
	 */
	public function setAutoJsonSerialization($flag) {
		$this->_autoJsonSerialization = ( bool ) $flag;
		return $this;
	}
	
	/**
	 * Get JSON context auto-serialization flag
	 *
	 * @return boolean
	 */
	public function getAutoJsonSerialization() {
		return $this->_autoJsonSerialization;
	}
	
	/**
	 * Set suffix from array
	 *
	 * @param  array $spec
	 * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface
	 */
	protected function _setSuffix(array $spec) {
		foreach ( $spec as $context => $suffixInfo ) {
			if (! is_string ( $context )) {
				$context = null;
			}
			
			if (is_string ( $suffixInfo )) {
				$this->setSuffix ( $context, $suffixInfo );
				continue;
			} elseif (is_array ( $suffixInfo )) {
				if (isset ( $suffixInfo ['suffix'] )) {
					$suffix = $suffixInfo ['suffix'];
					$prependViewRendererSuffix = true;
					
					if ((null === $context) && isset ( $suffixInfo ['context'] )) {
						$context = $suffixInfo ['context'];
					}
					
					if (isset ( $suffixInfo ['prependViewRendererSuffix'] )) {
						$prependViewRendererSuffix = $suffixInfo ['prependViewRendererSuffix'];
					}
					
					$this->setSuffix ( $context, $suffix, $prependViewRendererSuffix );
					continue;
				}
				
				$count = count ( $suffixInfo );
				switch (true) {
					case (($count < 2) && (null === $context)) :
						/**
						 * @see Zend_Controller_Action_Exception
						 */
						require_once 'Zend/Controller/Action/Exception.php';
						throw new Zend_Controller_Action_Exception ( 'Invalid suffix information provided in config' );
					case ($count < 2) :
						$suffix = array_shift ( $suffixInfo );
						$this->setSuffix ( $context, $suffix );
						break;
					case (($count < 3) && (null === $context)) :
						$context = array_shift ( $suffixInfo );
						$suffix = array_shift ( $suffixInfo );
						$this->setSuffix ( $context, $suffix );
						break;
					case (($count == 3) && (null === $context)) :
						$context = array_shift ( $suffixInfo );
						$suffix = array_shift ( $suffixInfo );
						$prependViewRendererSuffix = array_shift ( $suffixInfo );
						$this->setSuffix ( $context, $suffix, $prependViewRendererSuffix );
						break;
					case ($count >= 2) :
						$suffix = array_shift ( $suffixInfo );
						$prependViewRendererSuffix = array_shift ( $suffixInfo );
						$this->setSuffix ( $context, $suffix, $prependViewRendererSuffix );
						break;
				}
			}
		}
		return $this;
	}
	
	/**
	 * Customize view script suffix to use when switching context.
	 *
	 * Passing an empty suffix value to the setters disables the view script
	 * suffix change.
	 *
	 * @param  string  $context                   Context type for which to set suffix
	 * @param  string  $suffix                    Suffix to use
	 * @param  boolean $prependViewRendererSuffix Whether or not to prepend the new suffix to the viewrenderer suffix
	 * @throws Zend_Controller_Action_Exception
	 * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface
	 */
	public function setSuffix($context, $suffix, $prependViewRendererSuffix = true) {
		if (! isset ( $this->_contexts [$context] )) {
			/**
			 * @see Zend_Controller_Action_Exception
			 */
			require_once 'Zend/Controller/Action/Exception.php';
			throw new Zend_Controller_Action_Exception ( sprintf ( 'Cannot set suffix; invalid context type "%s"', $context ) );
		}
		
		if (empty ( $suffix )) {
			$suffix = '';
		}
		
		if (is_array ( $suffix )) {
			if (isset ( $suffix ['prependViewRendererSuffix'] )) {
				$prependViewRendererSuffix = $suffix ['prependViewRendererSuffix'];
			}
			if (isset ( $suffix ['suffix'] )) {
				$suffix = $suffix ['suffix'];
			} else {
				$suffix = '';
			}
		}
		
		$suffix = ( string ) $suffix;
		
		if ($prependViewRendererSuffix) {
			if (empty ( $suffix )) {
				$suffix = $this->_getViewRenderer ()->getViewSuffix ();
			} else {
				$suffix .= '.' . $this->_getViewRenderer ()->getViewSuffix ();
			}
		}
		
		$this->_contexts [$context] ['suffix'] = $suffix;
		return $this;
	}
	
	/**
	 * Retrieve suffix for given context type
	 *
	 * @param  string $type Context type
	 * @throws Zend_Controller_Action_Exception
	 * @return string
	 */
	public function getSuffix($type) {
		if (! isset ( $this->_contexts [$type] )) {
			/**
			 * @see Zend_Controller_Action_Exception
			 */
			require_once 'Zend/Controller/Action/Exception.php';
			throw new Zend_Controller_Action_Exception ( sprintf ( 'Cannot retrieve suffix; invalid context type "%s"', $type ) );
		}
		
		return $this->_contexts [$type] ['suffix'];
	}
	
	/**
	 * Does the given context exist?
	 *
	 * @param  string  $context
	 * @param  boolean $throwException
	 * @throws Zend_Controller_Action_Exception if context does not exist and throwException is true
	 * @return bool
	 */
	public function hasContext($context, $throwException = false) {
		if (is_string ( $context )) {
			if (isset ( $this->_contexts [$context] )) {
				return true;
			}
		} elseif (is_array ( $context )) {
			$error = false;
			foreach ( $context as $test ) {
				if (! isset ( $this->_contexts [$test] )) {
					$error = ( string ) $test;
					break;
				}
			}
			if (false === $error) {
				return true;
			}
			$context = $error;
		} elseif (true === $context) {
			return true;
		}
		
		if ($throwException) {
			/**
			 * @see Zend_Controller_Action_Exception
			 */
			require_once 'Zend/Controller/Action/Exception.php';
			throw new Zend_Controller_Action_Exception ( sprintf ( 'Context "%s" does not exist', $context ) );
		}
		
		return false;
	}
	
	/**
	 * Add header to context
	 *
	 * @param  string $context
	 * @param  string $header
	 * @param  string $content
	 * @throws Zend_Controller_Action_Exception
	 * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface
	 */
	public function addHeader($context, $header, $content) {
		$context = ( string ) $context;
		$this->hasContext ( $context, true );
		
		$header = ( string ) $header;
		$content = ( string ) $content;
		
		if (isset ( $this->_contexts [$context] ['headers'] [$header] )) {
			/**
			 * @see Zend_Controller_Action_Exception
			 */
			require_once 'Zend/Controller/Action/Exception.php';
			throw new Zend_Controller_Action_Exception ( sprintf ( 'Cannot add "%s" header to context "%s": already exists', $header, $context ) );
		}
		
		$this->_contexts [$context] ['headers'] [$header] = $content;
		return $this;
	}
	
	/**
	 * Customize response header to use when switching context
	 *
	 * Passing an empty header value to the setters disables the response
	 * header.
	 *
	 * @param  string $type   Context type for which to set suffix
	 * @param  string $header Header to set
	 * @param  string $content Header content
	 * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface
	 */
	public function setHeader($context, $header, $content) {
		$this->hasContext ( $context, true );
		$context = ( string ) $context;
		$header = ( string ) $header;
		$content = ( string ) $content;
		
		$this->_contexts [$context] ['headers'] [$header] = $content;
		return $this;
	}
	
	/**
	 * Add multiple headers at once for a given context
	 *
	 * @param  string $context
	 * @param  array  $headers
	 * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface
	 */
	public function addHeaders($context, array $headers) {
		foreach ( $headers as $header => $content ) {
			$this->addHeader ( $context, $header, $content );
		}
		
		return $this;
	}
	
	/**
	 * Set headers from context => headers pairs
	 *
	 * @param  array $options
	 * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface
	 */
	protected function _setHeaders(array $options) {
		foreach ( $options as $context => $headers ) {
			if (! is_array ( $headers )) {
				continue;
			}
			$this->setHeaders ( $context, $headers );
		}
		
		return $this;
	}
	
	/**
	 * Set multiple headers at once for a given context
	 *
	 * @param  string $context
	 * @param  array  $headers
	 * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface
	 */
	public function setHeaders($context, array $headers) {
		$this->clearHeaders ( $context );
		foreach ( $headers as $header => $content ) {
			$this->setHeader ( $context, $header, $content );
		}
		
		return $this;
	}
	
	/**
	 * Retrieve context header
	 *
	 * Returns the value of a given header for a given context type
	 *
	 * @param  string $context
	 * @param  string $header
	 * @return string|null
	 */
	public function getHeader($context, $header) {
		$this->hasContext ( $context, true );
		$context = ( string ) $context;
		$header = ( string ) $header;
		if (isset ( $this->_contexts [$context] ['headers'] [$header] )) {
			return $this->_contexts [$context] ['headers'] [$header];
		}
		
		return null;
	}
	
	/**
	 * Retrieve context headers
	 *
	 * Returns all headers for a context as key/value pairs
	 *
	 * @param  string $context
	 * @return array
	 */
	public function getHeaders($context) {
		$this->hasContext ( $context, true );
		$context = ( string ) $context;
		return $this->_contexts [$context] ['headers'];
	}
	
	/**
	 * Remove a single header from a context
	 *
	 * @param  string $context
	 * @param  string $header
	 * @return boolean
	 */
	public function removeHeader($context, $header) {
		$this->hasContext ( $context, true );
		$context = ( string ) $context;
		$header = ( string ) $header;
		if (isset ( $this->_contexts [$context] ['headers'] [$header] )) {
			unset ( $this->_contexts [$context] ['headers'] [$header] );
			return true;
		}
		
		return false;
	}
	
	/**
	 * Clear all headers for a given context
	 *
	 * @param  string $context
	 * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface
	 */
	public function clearHeaders($context) {
		$this->hasContext ( $context, true );
		$context = ( string ) $context;
		$this->_contexts [$context] ['headers'] = array ();
		return $this;
	}
	
	/**
	 * Validate trigger and return in normalized form
	 *
	 * @param  string $trigger
	 * @throws Zend_Controller_Action_Exception
	 * @return string
	 */
	protected function _validateTrigger($trigger) {
		$trigger = strtoupper ( $trigger );
		if ('TRIGGER_' !== substr ( $trigger, 0, 8 )) {
			$trigger = 'TRIGGER_' . $trigger;
		}
		
		if (! in_array ( $trigger, array (self::TRIGGER_INIT, self::TRIGGER_POST ) )) {
			/**
			 * @see Zend_Controller_Action_Exception
			 */
			require_once 'Zend/Controller/Action/Exception.php';
			throw new Zend_Controller_Action_Exception ( sprintf ( 'Invalid trigger "%s"', $trigger ) );
		}
		
		return $trigger;
	}
	
	/**
	 * Set a callback for a given context and trigger
	 *
	 * @param  string       $context
	 * @param  string       $trigger
	 * @param  string|array $callback
	 * @throws Zend_Controller_Action_Exception
	 * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface
	 */
	public function setCallback($context, $trigger, $callback) {
		$this->hasContext ( $context, true );
		$trigger = $this->_validateTrigger ( $trigger );
		
		if (! is_string ( $callback )) {
			if (! is_array ( $callback ) || (2 != count ( $callback ))) {
				/**
				 * @see Zend_Controller_Action_Exception
				 */
				require_once 'Zend/Controller/Action/Exception.php';
				throw new Zend_Controller_Action_Exception ( 'Invalid callback specified' );
			}
		}
		
		$this->_contexts [$context] ['callbacks'] [$trigger] = $callback;
		return $this;
	}
	
	/**
	 * Set callbacks from array of context => callbacks pairs
	 *
	 * @param  array $options
	 * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface
	 */
	protected function _setCallbacks(array $options) {
		foreach ( $options as $context => $callbacks ) {
			if (! is_array ( $callbacks )) {
				continue;
			}
			
			$this->setCallbacks ( $context, $callbacks );
		}
		return $this;
	}
	
	/**
	 * Set callbacks for a given context
	 *
	 * Callbacks should be in trigger/callback pairs.
	 *
	 * @param  string $context
	 * @param  array  $callbacks
	 * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface
	 */
	public function setCallbacks($context, array $callbacks) {
		$this->hasContext ( $context, true );
		$context = ( string ) $context;
		if (! isset ( $this->_contexts [$context] ['callbacks'] )) {
			$this->_contexts [$context] ['callbacks'] = array ();
		}
		
		foreach ( $callbacks as $trigger => $callback ) {
			$this->setCallback ( $context, $trigger, $callback );
		}
		return $this;
	}
	
	/**
	 * Get a single callback for a given context and trigger
	 *
	 * @param  string $context
	 * @param  string $trigger
	 * @return string|array|null
	 */
	public function getCallback($context, $trigger) {
		$this->hasContext ( $context, true );
		$trigger = $this->_validateTrigger ( $trigger );
		if (isset ( $this->_contexts [$context] ['callbacks'] [$trigger] )) {
			return $this->_contexts [$context] ['callbacks'] [$trigger];
		}
		
		return null;
	}
	
	/**
	 * Get all callbacks for a given context
	 *
	 * @param  string $context
	 * @return array
	 */
	public function getCallbacks($context) {
		$this->hasContext ( $context, true );
		return $this->_contexts [$context] ['callbacks'];
	}
	
	/**
	 * Clear a callback for a given context and trigger
	 *
	 * @param  string $context
	 * @param  string $trigger
	 * @return boolean
	 */
	public function removeCallback($context, $trigger) {
		$this->hasContext ( $context, true );
		$trigger = $this->_validateTrigger ( $trigger );
		if (isset ( $this->_contexts [$context] ['callbacks'] [$trigger] )) {
			unset ( $this->_contexts [$context] ['callbacks'] [$trigger] );
			return true;
		}
		
		return false;
	}
	
	/**
	 * Clear all callbacks for a given context
	 *
	 * @param  string $context
	 * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface
	 */
	public function clearCallbacks($context) {
		$this->hasContext ( $context, true );
		$this->_contexts [$context] ['callbacks'] = array ();
		return $this;
	}
	
	/**
	 * Set name of parameter to use when determining context format
	 *
	 * @param  string $name
	 * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface
	 */
	public function setContextParam($name) {
		$this->_contextParam = ( string ) $name;
		return $this;
	}
	
	/**
	 * Return context format request parameter name
	 *
	 * @return string
	 */
	public function getContextParam() {
		return $this->_contextParam;
	}
	
	/**
	 * Indicate default context to use when no context format provided
	 *
	 * @param  string $type
	 * @throws Zend_Controller_Action_Exception
	 * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface
	 */
	public function setDefaultContext($type) {
		if (! isset ( $this->_contexts [$type] )) {
			/**
			 * @see Zend_Controller_Action_Exception
			 */
			require_once 'Zend/Controller/Action/Exception.php';
			throw new Zend_Controller_Action_Exception ( sprintf ( 'Cannot set default context; invalid context type "%s"', $type ) );
		}
		
		$this->_defaultContext = $type;
		return $this;
	}
	
	/**
	 * Return default context
	 *
	 * @return string
	 */
	public function getDefaultContext() {
		return $this->_defaultContext;
	}
	
	/**
	 * Set flag indicating if layout should be disabled
	 *
	 * @param  boolean $flag
	 * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface
	 */
	public function setAutoDisableLayout($flag) {
		$this->_disableLayout = ($flag) ? true : false;
		return $this;
	}
	
	/**
	 * Retrieve auto layout disable flag
	 *
	 * @return boolean
	 */
	public function getAutoDisableLayout() {
		return $this->_disableLayout;
	}
	
	/**
	 * Add new context
	 *
	 * @param  string $context Context type
	 * @param  array  $spec    Context specification
	 * @throws Zend_Controller_Action_Exception
	 * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface
	 */
	public function addContext($context, array $spec) {
		if ($this->hasContext ( $context )) {
			/**
			 * @see Zend_Controller_Action_Exception
			 */
			require_once 'Zend/Controller/Action/Exception.php';
			throw new Zend_Controller_Action_Exception ( sprintf ( 'Cannot add context "%s"; already exists', $context ) );
		}
		$context = ( string ) $context;
		
		$this->_contexts [$context] = array ();
		
		$this->setSuffix ( $context, (isset ( $spec ['suffix'] ) ? $spec ['suffix'] : '') )->setHeaders ( $context, (isset ( $spec ['headers'] ) ? $spec ['headers'] : array ()) )->setCallbacks ( $context, (isset ( $spec ['callbacks'] ) ? $spec ['callbacks'] : array ()) );
		return $this;
	}
	
	/**
	 * Overwrite existing context
	 *
	 * @param  string $context Context type
	 * @param  array  $spec    Context specification
	 * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface
	 */
	public function setContext($context, array $spec) {
		$this->removeContext ( $context );
		return $this->addContext ( $context, $spec );
	}
	
	/**
	 * Add multiple contexts
	 *
	 * @param  array $contexts
	 * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface
	 */
	public function addContexts(array $contexts) {
		foreach ( $contexts as $context => $spec ) {
			$this->addContext ( $context, $spec );
		}
		return $this;
	}
	
	/**
	 * Set multiple contexts, after first removing all
	 *
	 * @param  array $contexts
	 * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface
	 */
	public function setContexts(array $contexts) {
		$this->clearContexts ();
		foreach ( $contexts as $context => $spec ) {
			$this->addContext ( $context, $spec );
		}
		return $this;
	}
	
	/**
	 * Retrieve context specification
	 *
	 * @param  string $context
	 * @return array|null
	 */
	public function getContext($context) {
		if ($this->hasContext ( $context )) {
			return $this->_contexts [( string ) $context];
		}
		return null;
	}
	
	/**
	 * Retrieve context definitions
	 *
	 * @return array
	 */
	public function getContexts() {
		return $this->_contexts;
	}
	
	/**
	 * Remove a context
	 *
	 * @param  string $context
	 * @return boolean
	 */
	public function removeContext($context) {
		if ($this->hasContext ( $context )) {
			unset ( $this->_contexts [( string ) $context] );
			return true;
		}
		return false;
	}
	
	/**
	 * Remove all contexts
	 *
	 * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface
	 */
	public function clearContexts() {
		$this->_contexts = array ();
		return $this;
	}
	
	/**
	 * Return current context, if any
	 *
	 * @return null|string
	 */
	public function getCurrentContext() {
		return $this->_currentContext;
	}
	
	/**
	 * Post dispatch processing
	 *
	 * Execute postDispatch callback for current context, if available
	 *
	 * @throws Zend_Controller_Action_Exception
	 * @return void
	 */
	public function postDispatch() {
		$context = $this->getCurrentContext ();
		if (null !== $context) {
			if (null !== ($callback = $this->getCallback ( $context, self::TRIGGER_POST ))) {
				if (is_string ( $callback ) && method_exists ( $this, $callback )) {
					$this->$callback ();
				} elseif (is_string ( $callback ) && function_exists ( $callback )) {
					$callback ();
				} elseif (is_array ( $callback )) {
					call_user_func ( $callback );
				} else {
					/**
					 * @see Zend_Controller_Action_Exception
					 */
					require_once 'Zend/Controller/Action/Exception.php';
					throw new Zend_Controller_Action_Exception ( sprintf ( 'Invalid postDispatch context callback registered for context "%s"', $context ) );
				}
			}
		}
	}
	
	/**
	 * JSON post processing
	 *
	 * JSON serialize view variables to response body
	 *
	 * @return void
	 */
	public function postJsonContext() {
		if (! $this->getAutoJsonSerialization ()) {
			return;
		}
		
		$viewRenderer = Zend_Controller_Action_HelperBroker::getStaticHelper ( 'viewRenderer' );
		$view = $viewRenderer->view;
		if ($view instanceof Zend_View_Interface) {
			/**
			 * @see Zend_Json
			 */
			if (method_exists ( $view, 'getVars' )) {
				require_once 'Zend/Json.php';
				$vars = Zend_Json::encode ( $view->getVars () );
				$this->getResponse ()->setBody ( $vars );
			} else {
				require_once 'Zend/Controller/Action/Exception.php';
				throw new Zend_Controller_Action_Exception ( 'View does not implement the getVars() method needed to encode the view into JSON' );
			}
		}
	}
	
	/**
	 * Add one or more contexts to an action
	 *
	 * @param  string       $action
	 * @param  string|array $context
	 * @return Zend_Controller_Action_Helper_ContextSwitch|void Provides a fluent interface
	 */
	public function addActionContext($action, $context) {
		$this->hasContext ( $context, true );
		$controller = $this->getActionController ();
		if (null === $controller) {
			return;
		}
		$action = ( string ) $action;
		$contextKey = $this->_contextKey;
		
		if (! isset ( $controller->$contextKey )) {
			$controller->$contextKey = array ();
		}
		
		if (true === $context) {
			$contexts = $this->getContexts ();
			$controller->{$contextKey} [$action] = array_keys ( $contexts );
			return $this;
		}
		
		$context = ( array ) $context;
		if (! isset ( $controller->{$contextKey} [$action] )) {
			$controller->{$contextKey} [$action] = $context;
		} else {
			$controller->{$contextKey} [$action] = array_merge ( $controller->{$contextKey} [$action], $context );
		}
		
		return $this;
	}
	
	/**
	 * Set a context as available for a given controller action
	 *
	 * @param  string       $action
	 * @param  string|array $context
	 * @return Zend_Controller_Action_Helper_ContextSwitch|void Provides a fluent interface
	 */
	public function setActionContext($action, $context) {
		$this->hasContext ( $context, true );
		$controller = $this->getActionController ();
		if (null === $controller) {
			return;
		}
		$action = ( string ) $action;
		$contextKey = $this->_contextKey;
		
		if (! isset ( $controller->$contextKey )) {
			$controller->$contextKey = array ();
		}
		
		if (true === $context) {
			$contexts = $this->getContexts ();
			$controller->{$contextKey} [$action] = array_keys ( $contexts );
		} else {
			$controller->{$contextKey} [$action] = ( array ) $context;
		}
		
		return $this;
	}
	
	/**
	 * Add multiple action/context pairs at once
	 *
	 * @param  array $contexts
	 * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface
	 */
	public function addActionContexts(array $contexts) {
		foreach ( $contexts as $action => $context ) {
			$this->addActionContext ( $action, $context );
		}
		return $this;
	}
	
	/**
	 * Overwrite and set multiple action contexts at once
	 *
	 * @param  array $contexts
	 * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface
	 */
	public function setActionContexts(array $contexts) {
		foreach ( $contexts as $action => $context ) {
			$this->setActionContext ( $action, $context );
		}
		return $this;
	}
	
	/**
	 * Does a particular controller action have the given context(s)?
	 *
	 * @param  string       $action
	 * @param  string|array $context
	 * @throws Zend_Controller_Action_Exception
	 * @return boolean
	 */
	public function hasActionContext($action, $context) {
		$this->hasContext ( $context, true );
		$controller = $this->getActionController ();
		if (null === $controller) {
			return false;
		}
		$action = ( string ) $action;
		$contextKey = $this->_contextKey;
		
		if (! isset ( $controller->{$contextKey} )) {
			return false;
		}
		
		$allContexts = $controller->{$contextKey};
		
		if (! is_array ( $allContexts )) {
			/**
			 * @see Zend_Controller_Action_Exception
			 */
			require_once 'Zend/Controller/Action/Exception.php';
			throw new Zend_Controller_Action_Exception ( "Invalid contexts found for controller" );
		}
		
		if (! isset ( $allContexts [$action] )) {
			return false;
		}
		
		if (true === $allContexts [$action]) {
			return true;
		}
		
		$contexts = $allContexts [$action];
		
		if (! is_array ( $contexts )) {
			/**
			 * @see Zend_Controller_Action_Exception
			 */
			require_once 'Zend/Controller/Action/Exception.php';
			throw new Zend_Controller_Action_Exception ( sprintf ( "Invalid contexts found for action '%s'", $action ) );
		}
		
		if (is_string ( $context ) && in_array ( $context, $contexts )) {
			return true;
		} elseif (is_array ( $context )) {
			$found = true;
			foreach ( $context as $test ) {
				if (! in_array ( $test, $contexts )) {
					$found = false;
					break;
				}
			}
			return $found;
		}
		
		return false;
	}
	
	/**
	 * Get contexts for a given action or all actions in the controller
	 *
	 * @param  string $action
	 * @return array
	 */
	public function getActionContexts($action = null) {
		$controller = $this->getActionController ();
		if (null === $controller) {
			return array ();
		}
		$action = ( string ) $action;
		$contextKey = $this->_contextKey;
		
		if (! isset ( $controller->$contextKey )) {
			return array ();
		}
		
		if (null !== $action) {
			if (isset ( $controller->{$contextKey} [$action] )) {
				return $controller->{$contextKey} [$action];
			} else {
				return array ();
			}
		}
		
		return $controller->$contextKey;
	}
	
	/**
	 * Remove one or more contexts for a given controller action
	 *
	 * @param  string       $action
	 * @param  string|array $context
	 * @return boolean
	 */
	public function removeActionContext($action, $context) {
		if ($this->hasActionContext ( $action, $context )) {
			$controller = $this->getActionController ();
			$contextKey = $this->_contextKey;
			$action = ( string ) $action;
			$contexts = $controller->$contextKey;
			$actionContexts = $contexts [$action];
			$contexts = ( array ) $context;
			foreach ( $contexts as $context ) {
				$index = array_search ( $context, $actionContexts );
				if (false !== $index) {
					unset ( $controller->{$contextKey} [$action] [$index] );
				}
			}
			return true;
		}
		return false;
	}
	
	/**
	 * Clear all contexts for a given controller action or all actions
	 *
	 * @param  string $action
	 * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface
	 */
	public function clearActionContexts($action = null) {
		$controller = $this->getActionController ();
		$contextKey = $this->_contextKey;
		
		if (! isset ( $controller->$contextKey ) || empty ( $controller->$contextKey )) {
			return $this;
		}
		
		if (null === $action) {
			$controller->$contextKey = array ();
			return $this;
		}
		
		$action = ( string ) $action;
		if (isset ( $controller->{$contextKey} [$action] )) {
			unset ( $controller->{$contextKey} [$action] );
		}
		
		return $this;
	}
	
	/**
	 * Retrieve ViewRenderer
	 *
	 * @return Zend_Controller_Action_Helper_ViewRenderer Provides a fluent interface
	 */
	protected function _getViewRenderer() {
		if (null === $this->_viewRenderer) {
			$this->_viewRenderer = Zend_Controller_Action_HelperBroker::getStaticHelper ( 'viewRenderer' );
		}
		
		return $this->_viewRenderer;
	}
}

